How do I access / display an image that is part of...
# help-with-umbraco
m
Noob question here, but in my block list I have an image selector from the media library. I am running a foreach loop to go through the timelineItems and I can see the timelineimage is available, I am not sure how I to display that image in razor ? https://cdn.discordapp.com/attachments/1227977954626240542/1227977954957463656/image.png?ex=662a5e97&is=6617e997&hm=1ce5aa60edeca3ac7cff40c22a2f42f3d9cff4a0f3a26549c0a1ce5c58d7f9fb&
I have tried a couple of options:
not sure if Umbraco has a helper function to display media library images
a
Try:
Copy code
<img src="@item.Content.Value<MediaWithCrops>("timelineimage").Url" class="img-fluid" />
But you could do this as part of your loop (if you use models builder):
Copy code
@foreach(var item in Model.Items.Select(x => x.Content).OfType<MyTimelineItemModel>())
{
<img src="@item.Timelineimage.Url" class="img-fluid" />
}
(This assumes
Model
is your page/block doc type, and
Items
is your block list) Hope this helps! πŸ™‚
m
Let me give it a shot
For reference here is my foreach, I am able to get the rest of the values, I tried your code above but got an error "cannot convert from "method group" to "object": @{ counter = 0; foreach (var item in timelineItems) { var timelineYearString = item.Content.Value("timelineyear").ToString(); // Convert to string var timelineYear = DateTime.Parse(timelineYearString).Year; <img src="@item.Content.Value("timelineimage").Url" class="img-fluid" /> @(timelineYear) @(item.Content.Value("timelinecontent")) counter++; } }
How do I paste formatted code?
πŸ˜„
m
h
use three ` before and after the code πŸ™‚
m
How are you getting
timelineItems
?
s
three backticks around code block.. and maybe a language if you like
Copy code
csharp
var x = 100;
m
Copy code
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage
@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels;
@using Umbraco.Cms.Core.Models.Blocks;
@{
    var nodeId = int.Parse(ViewData["id"].ToString());
    var node = Umbraco.Content(nodeId);
    var timelineItems = node.Value<IEnumerable<BlockListItem>>("timelineItems");
}
I need to emphasize this project is a balls up, the previous developers had a very weird way of handling document types (there is literally a single page.cshtml which does a switch statement like this:
Copy code
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage
@using Umbraco.Cms.Web
@{
    var modules = Model.Children;
}

@foreach (var module in modules)
{
    var m = module.ContentType.Alias.ToLower();
    var test = "123";
    switch(module.ContentType.Alias.ToLower())
    {
        case "timeline":
            @await Html.PartialAsync("~/Views/Partials/Modules/timeline.cshtml", null, new ViewDataDictionary(ViewData) { { "id", module.Id }})
            break;
as we got the project many years later (and not being a c# developer off heart, I am hacking my way through bad design and undestanding c# πŸ˜„
so content pages are added using a page.cshtml which in return has the following "Partial":
Copy code
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage
@{
    Layout = "master.cshtml";
}

@Html.Partial("rendering/render-modules")
Then we have a switch statement in render-modules which literally has to be updated for each document type (so when we want to add a new document type, that damn switch statement needs to be updated
Hi everyone, i'm back on this today, i'm thinking maybe im not collecting the data in the way I should
if there is possibly a better way to do it
with my lack of understanding the constructs of c# objects and how to obtain the specific types from data maybe I should not be using Umbraco.Content(nodeId) to obtain the actual data of that content but another way which could simplify the access to the various items in the blocklist
It seems simple enough with normal properties where it's string values, but seems that media is a bit more tricky
For reference, here is my partial view for the timeline:
a
Yeah I'd say you don't need your
nodeId
and
node
variables. I'm guessing the file you attached is for the whole page template? If so
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage
should give you your
Model
containing the equivalent to your old
node
variable. If you use models builder, you could strongly type key aspects of your code (as per my last message). But you could replace the first
@inherits
line with something like
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<TimelinePage>
which would turn your
Model
from
IPublishedContent
to
TimelinePage
(assuming TimelinePage is your pages doc type alias). That way you'd be able to access your doc type properties directly, rather than having to explicitly use the
.Value("")
extension method each time. However, if you don't use models builder I'd recommend for strings (or any other type) to use
.Value<string>("alias")
. This way they're explicitly converted and parsed if the data allows.
m
Unfortunately not, in the content editor the previous developer added document types underneath the page (so in theory I guess they can be accessed directly), so the page.cshtml has a switch statement to look at what type of node is loaded (so in this case timeline) and then loads a partial for that node
Very bad design as it makes it virtually impossible to follow documentation, but we were given the project 5 years later and client does not want to pay to get it redone
1 sec let me try make some of the changes, maybe you can then see if it will work
a
a single page template with a switch statement to load in different partials based on doc type? wow that's certainly one way to do it.
m
so the page view template looks like this:
Copy code
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage
@{
    Layout = "master.cshtml";
}

@Html.Partial("rendering/render-modules")
and render-modules
Copy code
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage
@using Umbraco.Cms.Web
@{
    var modules = Model.Children;
}

@foreach (var module in modules)
{
    switch(module.ContentType.Alias.ToLower())
    {
        case "timeline":
            @await Html.PartialAsync("~/Views/Partials/Modules/timeline.cshtml", null, new ViewDataDictionary(ViewData) { { "id", module.Id }})
            break;
        case "pageheader":
            @await Html.PartialAsync("~/Views/Partials/Modules/page-header.cshtml", null, new ViewDataDictionary(ViewData) { { "id", module.Id }})
        break;
        case "pagevideo":
            @await Html.PartialAsync("~/Views/Partials/Modules/page-video.cshtml", null, new ViewDataDictionary(ViewData) { { "id", module.Id }})
        break;
        case "genericsection":
            @await Html.PartialAsync("~/Views/Partials/Modules/generic-section.cshtml", null, new ViewDataDictionary(ViewData) { { "id", module.Id }})
        break;
not the entire page, there is like 40 "render modules"
so everytime we create a new document type, we need to modify this flippen switch statement
Given where I am, I am happy to make changes, maybe create a new template alltogether?
so I've been copying what they did and for most part it worked, with the exception of this one where the blocklist has a bit more than just single values but contains for example media items
so getting something like "title" is easy, but accessing the media item from the Umbraco.Content helper seems a bit more challenging
40-years-of-dra in this case is the timeline blocklist
where the page name is 40 years .../ and I also called the document type here the same, but I am sure you get the picture
I cannot imagine this was how umbraco intended this to work and as this was my first umbraco site I assumed this was the way
but took me ages to figure out what was going on (had no handover, were given the source code with a handshake and godspeed)
a
Your
module
should still be an
IPublishedContent
type which you could give to the 2nd parameter of the
PartialAsync()
method, thus negating the need to provide a
ViewDataDictionary
for the 3rd parameter. Your partial should then just be able to utilise the
Model
. But to be honest, I'd look at overhauling how this is structured. Personally I'd go through each doc type, create the necessary template, set as default, remove the current template, copy the partial code over to the new template (with perhaps some slight adapting). This would make it fall in line with a typical Umbraco setup and make it less confusing for future developers.
m
Agreed, i'll need to propose this to the powers, but there is no maintenance plan in place so we only add "features" when needed
a
For images, you could do
node.Value<MediaWithCrops>("imageAlias")
to parse into something you can pull the URL from
m
let me try that quickly
if that works I'll buy you a beer πŸ˜„
Unless you're in the UK, my currency has a 1/25 ratio and I think you guys pay premium for lager πŸ˜„
p
@User May I suggest β€œfriends” instead of "guys"? We use gender inclusive language in this Discord. πŸ˜€
a
haha, yeah from the UK, but don't worry about it, happy to help where I can
You may need to do the following to access
MediaWithCrops
@using Umbraco.Cms.Core.Models
m
According to Probot I should have said I think you friends pay premium for lager πŸ˜„
1 sec let me give this a go, and thank you for trying to help
Worth noting I am iterating through node using a for loop
I have added this to the top of the timeline.cshtml:
Copy code
@inherits UmbracoViewPage<BlockListItem>
@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels;
@using Umbraco.Cms.Core.Models.Blocks;
@using Umbraco.Cms.Core.Models;
@{
    var nodeId = int.Parse(ViewData["id"].ToString());
    var node = Umbraco.Content(nodeId);
    var timelineItems = node.Value<IEnumerable<BlockListItem>>("timelineItems");
}
i then for loop timelineItems
it doesnt seem that I can do item.Value but item.content.value
sorry if I missed something
Copy code
foreach (var item in timelineItems)
{
    var timelineYearString = item.Content.Value("timelineyear").ToString(); // Convert to string
    var timelineYear = DateTime.Parse(timelineYearString).Year;
    var image = item.Content.Value<MediaWithCrops>.get("timelineimage");
....
node contains multiple timelineitems for reference
1980,1981 etc
each with its own dataset
a
Yeah so your item will be a
BlockListItem
which has
Content
and
Settings
. Both of these will have
IPublishedElement
but should still contain the ability to do
item.Content.Value()
. Your code above for the image variable needs changing to:
var image = item.Content.Value<MediaWithCrops>("timelineimage");
(without the
get()
)
Shouldn't really matter how many you've got
So long as they're all the same doc type which adhere to the same schema, your code consistently interprets it as all the same.
m
a
Excellent, so do
image.Url()
and you're winning
m
MY FRIEND!
πŸ˜„
hours and hours and hours
a
excellent! 😁
m
You've made my day
a
ah you're welcome, glad I could help. If you'd like to expand on just returning a full fat image from the media library and pull through a more optimised image for your content, then consider looking into image cropper. You essentially define a crop alias against the media selector (data type) where you can set a width & height (essentially an aspect ratio) which modifies the rendered image being pulled through at the exact size you specified. Keeps images consistent and prevents lazy content editors from providing incorrect image sizes. Some info here: https://docs.umbraco.com/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/image-cropper#sample-code
m
will have a look at that
that's a very smart feature
a
The brain power behind it all is ImageSharp. Umbraco Image Cropper I guess is a glorified URL generator. Here's the docs which shows some examples of what parameters Umbraco utilises (i.e. width, height, quality, rxy, etc): https://docs.sixlabors.com/articles/imagesharp.web/processingcommands.html
30 Views